package com.colladaviewer.android.camera;

import java.nio.ByteBuffer;
import java.nio.ShortBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;

import jp.nyatla.nyartoolkit.core.types.matrix.NyARDoubleMatrix44;

import android.content.Context;
import android.graphics.Bitmap;

import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.colladaviewer.ardetector.ARDetector;

import com.jme3.math.Matrix4f;
import com.jme3.scene.Spatial;
import com.jme3.system.JmeSystem;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.texture.Image.Format;
import com.colladaviewer.app.R;

public class AndroidARManager implements SurfaceHolder.Callback
{
    private final static Logger logger = Logger.getLogger(AndroidARManager.class.getName());
    
    private static AndroidARManager instance = null;
    
    public volatile SurfaceView previewView = null;
    private volatile ARCameraThread camThread = null;
    
    public static synchronized AndroidARManager get(Context context, SurfaceHolder glsurfaceHolder)
    {
        if (instance == null)
        {
            if (context != null && glsurfaceHolder != null)
            {
                instance = new AndroidARManager(context, glsurfaceHolder);
            }
        }
        return instance;
        
    }
    
    private AndroidARManager(Context context, SurfaceHolder glsurfaceHolder)
    {
        //super(context);
        glsurfaceHolder.addCallback(this);
        
        previewView = new SurfaceView(context);
        //addView(previewView);

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        previewView.getHolder().addCallback(this);
        previewView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        
        
        if (camThread == null) 
        {
            camThread = new ARCameraThread(previewView.getHolder(), previewView.getWidth(), previewView.getHeight());
            //camThread = new ARCameraThread(glsurfaceHolder, previewView.getWidth(), previewView.getHeight());
    
            camThread.start();
        } 
         
    }
    
    /**
     * Returns an exception or null if no error
     * @return
     */
    public synchronized Throwable getException()
    {
        return camThread == null ? null : camThread.getException(); 
        
    }
    
    public Texture getTexture()
    {
        Texture texture = null;
        if (camThread != null) 
        { 
            texture = camThread.getTexture();
        }
        return texture;
    }
    
    /**
     * <code>applyDetectedMatrix</code> sets the location and rotation of spatial 
     * to that of the last detected marker 
     * @param spatial
     * @return true if set, false else
     */
    public boolean applyDetectedMatrix(final Spatial spatial)
    {
        if (camThread != null) 
        { 
            return camThread.applyDetectedMatrix(spatial);
        }
        return false;
    }
    
    
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) 
    {
        /*
        surfaceDestroyed(holder);
        
        if (camThread == null) 
        {
            camThread = new ARCameraThread(previewView.getHolder(), previewView.getWidth(), previewView.getHeight());
            camThread.start();
        }
        */
               
    }


    public void surfaceCreated(SurfaceHolder holder) 
    {    
        /*
        if (camThread == null) 
        {
            camThread = new ARCameraThread(previewView.getHolder(), previewView.getWidth(), previewView.getHeight());
            camThread.start();
        } 
        */                       
    }

    public void surfaceDestroyed(SurfaceHolder holder) 
    {
        if (camThread != null) 
        {            
            camThread.stop();
            camThread = null;
        }        
    }
    
    /**
     * <code>ARCameraThread</code> fetches the pictures from the camera and scans them for AR markers
     * @author as
     *
     */
    private class ARCameraThread implements Runnable
    {
        private volatile Thread thread = null;
        private volatile boolean stop;

        private volatile Texture lastTexture = null;
        private volatile boolean textureChanged = false;
        
        private volatile ARDetector ardetector;
        
        private volatile float confidence = 0f;               
        private SurfaceHolder surfaceHolder;    
        
        private Throwable exception = null;
                
                
        private int width;
        private int height;
        
        private int[] pixels;
        private byte[] buf;
        private ShortBuffer dst;
        private short[] shortBuf;

        public ARCameraThread(final SurfaceHolder surfaceHolder, final int width, final int height) 
        {            
            stop = false;
            this.surfaceHolder = surfaceHolder;
            this.width = width;
            this.height = height;            
        }

        public synchronized Texture getTexture()
        {
            Texture result = null;
            if (textureChanged)
            {
                textureChanged = false;
                result = lastTexture;
            }
            return result;
        }
        
        /**
         * Returns an exception or null if no error
         * @return
         */
        public synchronized Throwable getException()
        {
            return exception;
        }

        /**
         * <code>applyDetectedMatrix</code> sets the location and rotation of spatial 
         * to that of the last detected marker 
         * @param spatial
         * @return true if set, false else
         */
        public synchronized boolean applyDetectedMatrix(final Spatial spatial)
        {            
            if (ardetector != null)
            {
                return ardetector.applyDetectedMatrix(spatial);   
            }
            return false;
        }
        
        public synchronized void start()
        {
            if (thread == null)
            {
                thread = new Thread(this);
                thread.setDaemon(true);
                thread.setName("ARCameraThread");
                thread.start();
            }
        }
          
        public synchronized void stop()
        {
            if (thread != null)
            {
                stop = true;
                interrupt();  
                try {
                    thread.join(7000);
                } catch (InterruptedException e) 
                {
                    logger.severe("Failed to stop ARCameraThread: " + e.getMessage());
                }               
                thread = null;                
            }
              
        }
          
        public synchronized void interrupt()
        {
            if (thread != null)
            {
                thread.interrupt();
            }
        }          
        
        public void run()        
        {
            dst = null;
            CameraSource cs = null;
            try 
            {          
                // Open cam first to get the preview size
                cs = new GenuineCamera(surfaceHolder, width, height);
                if (cs.open()) 
                {
                width = cs.getWidth();
                height = cs.getHeight();
                    ardetector = new ARDetector(JmeSystem.getResources().openRawResource( R.raw.barcode));
                    
                    Bitmap bitmap;            
                    while ( ! stop ) 
                    {                
                        // Get next picture from the camera
                        bitmap = cs.getLastBitmap();                     
                        if (bitmap != null)
                        {
                            convertBitmapToR5G6B5_16(bitmap);
                            
                            Texture texture = convertToTexture(bitmap);
                            synchronized(this)
                            {                    
                                lastTexture = texture;
                                textureChanged = true;
                            }
                            if (ardetector != null)
                            {
                                synchronized(this)
                                {
                                    if ( ! ardetector.getDetected())
                                    {
                                        // Only scan when the results from last scan have been fetched 
                                        confidence = ardetector.scan(shortBuf, width, height, null);
                                    }
                                }
                                    
                            }
                        }
                        else
                        {
                            Thread.sleep(100);
                        }
                   
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) { }
                            
                    } // end while !done                
                    
                }   // end if camera open()     
                else
                {
                    logger.log(Level.SEVERE, "ARCameraThread failed to open camera");   
                    exception = cs.getException();
                }
                
            } 
     
            catch (Exception e) 
            {
                logger.log(Level.SEVERE, "ARCameraThread: " + e.getMessage());
                exception = e;
            }
            
            // Make sure to release the CameraDevice
            try
            {            
                if (cs != null) 
                {
                    cs.close();
                    logger.info("Camera was closed");                    
                }
            }
            catch (Exception e) 
            {
                logger.log(Level.SEVERE, "ARCameraThread failed to close camera: " + e.getMessage());
            }

            logger.info("ARCameraThread terminated");
        }
        
        public boolean convertBitmapToR5G6B5_16(final Bitmap bitmap)
        {
                                
            if (bitmap != null)
            {
                if (bitmap.getConfig() == Bitmap.Config.RGB_565)
                {
                    if (dst == null)
                    {
                        dst = ShortBuffer.allocate(bitmap.getWidth() * bitmap.getHeight());
                    }
                    dst.rewind();
                    bitmap.copyPixelsToBuffer(dst);
                    dst.rewind();                    
                    if (shortBuf == null)
                    {
                        shortBuf = new short[bitmap.getWidth() * bitmap.getHeight()];
                    }
                    dst.get(shortBuf);
                    return true;
                }
                else
                {
                    if (bitmap.getWidth() != width || bitmap.getHeight() != height || buf == null) 
                    {
                        width = bitmap.getWidth();
                        height = bitmap.getHeight();
                        pixels = new int[width * height];
                        buf = new byte[pixels.length * 3];                   
                    }
               
                    // time = System.currentTimeMillis();
                    bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
    
                    for (int i = 0; i < pixels.length; i++) 
                    {
                        int argb = pixels[i];
                        // byte a = (byte) (argb & 0xFF000000 >> 24);
                        byte r = (byte) (argb & 0x00FF0000 >> 16);
                        byte g = (byte) (argb & 0x0000FF00 >> 8);
                        byte b = (byte) (argb & 0x000000FF);
                        buf[i * 3] = r;
                        buf[i * 3 + 1] = g;
                        buf[i * 3 + 2] = b;
                    }
                    return true;
                }
            }
            return false;
        }        
        
        private Texture convertToTexture(Bitmap bitmap)
        {            
            Texture texture = null;
            if (bitmap != null)
            {
                int width = bitmap.getWidth();
                int height = bitmap.getHeight();
                int bytesPerPixel = -1;
                Format fmt;
    
                switch (bitmap.getConfig()){
                    case ALPHA_8:
                        bytesPerPixel = 1;
                        fmt = Format.Alpha8;
                        break;
                    case ARGB_4444:
                        bytesPerPixel = 2;
                        fmt = Format.ARGB4444;
                        break;
                    case ARGB_8888:
                        bytesPerPixel = 4;
                        fmt = Format.RGBA8;
                        break;
                    case RGB_565:
                        bytesPerPixel = 2;
                        fmt = Format.RGB565;
                        break;
                    default:
                        return null;
                }
                
                Image image = new Image(fmt, width, height, null);
                image.setEfficentData(bitmap);                
                texture = new Texture2D(image);                
            }
            return texture;
        }
        
        public void toCameraViewRH(NyARDoubleMatrix44 mat,double i_scale, Matrix4f resultMatrix)
        {
            resultMatrix.m00 = (float)mat.m00;
            resultMatrix.m10 = (float)mat.m10;            
            resultMatrix.m20 = (float)mat.m20;
            resultMatrix.m30 = 0f;
            resultMatrix.m01 = (float)mat.m01;
            resultMatrix.m11 = (float)mat.m11;            
            resultMatrix.m21 = (float)mat.m21;
            resultMatrix.m31 = 0f;
            resultMatrix.m02 = (float)mat.m02;
            resultMatrix.m12 = (float)mat.m12;            
            resultMatrix.m22 = (float)mat.m22;
            resultMatrix.m32 = 0f;
            double scale=1/i_scale;
            resultMatrix.m03 = (float)(mat.m03*scale);
            resultMatrix.m13 = (float)(mat.m13*scale);    
            resultMatrix.m23 = (float)(mat.m23*scale);
            resultMatrix.m33 = 1f;
            
            /*o_gl_result[0 + 0 * 4] = mat.m00; 
            o_gl_result[1 + 0 * 4] = -mat.m10;
            o_gl_result[2 + 0 * 4] = -mat.m20;
            o_gl_result[3 + 0 * 4] = 0.0;
            o_gl_result[0 + 1 * 4] = mat.m01;
            o_gl_result[1 + 1 * 4] = -mat.m11;
            o_gl_result[2 + 1 * 4] = -mat.m21;
            o_gl_result[3 + 1 * 4] = 0.0;
            o_gl_result[0 + 2 * 4] = mat.m02;
            o_gl_result[1 + 2 * 4] = -mat.m12;
            o_gl_result[2 + 2 * 4] = -mat.m22;
            o_gl_result[3 + 2 * 4] = 0.0;
            
            
            double scale=1/i_scale;
            o_gl_result[0 + 3 * 4] = mat.m03*scale;
            o_gl_result[1 + 3 * 4] = -mat.m13*scale;
            o_gl_result[2 + 3 * 4] = -mat.m23*scale;
            o_gl_result[3 + 3 * 4] = 1.0;
            */
            return;
        }
    }


}
